package net.oschina.oldpig.sewer

import com.typesafe.scalalogging.log4j.Logging
import javafx.stage.{FileChooser, Stage}
import javafx.scene.layout._
import javafx.scene.{Cursor, Scene}
import javafx.scene.media.{MediaView, Media, MediaPlayer}
import javafx.scene.media.MediaPlayer.Status
import java.io.File
import com.google.common.io.Files
import javafx.geometry.{Pos, Insets}
import javafx.scene.control._
import javafx.event.{EventHandler, ActionEvent}
import javafx.util.Duration
import javafx.beans.value.{ObservableValue, ChangeListener}
import javafx.scene.image.{ImageView, Image}
import javafx.application.Platform
import javafx.scene.input.MouseEvent
import net.liftweb.json._
import java.net.URL


case class ParleysStream(duration: Long, remote: String, local: String)

case class Slide(cuePoint: Long, remote: String, local: String)


class SewerMain extends javafx.application.Application with Logging {

  def init(primaryStage: Stage) {
    primaryStage.setScene(new Scene(SewerView))
//    primaryStage.setScene(new Scene(MediaControl))
  }

  override def start(primaryStage: Stage) {
    init(primaryStage)
    primaryStage.show()
  }

  override def stop() {
    val player = MediaControl.mediaView.getMediaPlayer
    if (player != null)
      player.stop()
  }
}

object SewerMain extends App {
  javafx.application.Application.launch(classOf[SewerMain], args: _*)
}

object MediaControl extends BorderPane with Logging {
  implicit val formats = DefaultFormats

  var mediaView: MediaView = new MediaView()
  val slideView = new ImageView()
  slideView.setPreserveRatio(true)
  slideView.setFitWidth(512)
  val mvPane = new HBox(5)
  mvPane.getChildren.add(mediaView)
  mvPane.getChildren.add(slideView)
  mvPane.setStyle("-fx-background-color: black;")
  setCenter(mvPane)

  val mediaBar = new HBox(8)
  var stopRequested, atEndOfMedia = false
  var duration: Duration = _
  mediaBar.setPadding(new Insets(5, 10, 5, 10))
  mediaBar.setAlignment(Pos.CENTER_LEFT)
  BorderPane.setAlignment(mediaBar, Pos.CENTER)

  val imageViewPlay = new ImageView(new Image(this.getClass.getResourceAsStream("/playbutton.png")))
  val imageViewPause = new ImageView(new Image(this.getClass.getResourceAsStream("/pausebutton.png")))

  val playButton = new Button()
  playButton.setMinWidth(Control.USE_PREF_SIZE)
  playButton.setGraphic(imageViewPlay)
  playButton.setOnAction(new EventHandler[ActionEvent]() {
    override def handle(e: ActionEvent) {
      updateValues()
      val player = mediaView.getMediaPlayer
      player.getStatus match {
        case Status.UNKNOWN | Status.HALTED =>
        case Status.PAUSED | Status.READY | Status.STOPPED =>
          if (atEndOfMedia) {
            player.seek(player.getStartTime)
            atEndOfMedia = false
            playButton.setGraphic(imageViewPlay)
            updateValues()
          }
          player.play()
          playButton.setGraphic(imageViewPause)
        case _ => player.pause()
      }
    }
  })
  mediaBar.getChildren.add(playButton)

  val progressBar = new ProgressBar
  progressBar.setMinWidth(30)
  progressBar.setMaxWidth(Double.MaxValue)
  HBox.setHgrow(progressBar, Priority.ALWAYS)
  progressBar.setOnMousePressed(new EventHandler[MouseEvent] {
    def mediaAtPos(pos: Double): Int = {
      val borders = streams.map(_.duration).scan(0L)(_ + _)
      (for ((_, i) <- borders.zipWithIndex.find {
        case (border, i) =>
          border >= pos * overallDuration / progressBar.getWidth
      }) yield i - 1) getOrElse currentStream
    }

    def handle(e: MouseEvent) {
      val pointStream = mediaAtPos(e.getX)
      overallPosition = overallDuration * e.getX / progressBar.getWidth
      if (pointStream != currentStream) {
        playStream(pointStream, overallPosition - streams.slice(0, pointStream).map(_.duration).sum)
      } else {
        mediaView.getMediaPlayer.setStartTime(Duration.ZERO)
        mediaView.getMediaPlayer.seek(Duration.millis(
          overallPosition - streams.slice(0, pointStream).map(_.duration).sum
        ))
      }
    }
  })
  progressBar.setCursor(Cursor.TEXT)
  mediaBar.getChildren.add(progressBar)

  val playTime = new Label("playTime")
  playTime.setMinWidth(Control.USE_PREF_SIZE)
  mediaBar.getChildren.add(playTime)

  val loadButton = new Button
  loadButton.setText("Load Package")
  loadButton.setMinWidth(Control.USE_PREF_SIZE)
  loadButton.setOnAction(new EventHandler[ActionEvent] {
    def handle(p1: ActionEvent) {
      val chooser = new FileChooser()
      chooser.getExtensionFilters.add(
        new FileChooser.ExtensionFilter("Sewer Package(*.sew)", "*.sew")
      )
      chooser.setTitle("Select Sewer Package")
      val selectedFile = chooser.showOpenDialog(MediaControl.getScene.getWindow)
      if (selectedFile != null)
        loadPackage(selectedFile)
    }
  })
  mediaBar.getChildren.add(loadButton)

  val volumeLabel = new Label("Vol")
  volumeLabel.setMinWidth(Control.USE_PREF_SIZE)
  mediaBar.getChildren.add(volumeLabel)

  val volumeSlider = new Slider
  volumeSlider.setPrefWidth(70)
  volumeSlider.setMinWidth(30)
  volumeSlider.maxWidth(Region.USE_PREF_SIZE)
  volumeSlider.setMax(100)
  volumeSlider.valueProperty().addListener(new ChangeListener[Number] {
    def changed(observable: ObservableValue[_ <: Number], oldValue: Number, newValue: Number) {
      if (volumeSlider.isValueChanging) {
        if (mediaView.getMediaPlayer != null) {
          mediaView.getMediaPlayer.setVolume(volumeSlider.getValue / volumeSlider.getMax)
        }
      }
    }
  })
  mediaBar.getChildren.add(volumeSlider)
  setBottom(mediaBar)

  var pkgLoader: JarClassLoader = _
  var timelines: List[Duration] = _

  def parseTimeline(line: String) = {
    val Extractor = """(\d{2}):(\d{2}):(\d{2})\.(\d{3})""".r
    val millis = line match {
      case Extractor(hour, minute, second, milli) => (hour.toLong * 60 * 60 + minute.toLong * 60 + second.toLong) * 1000 + milli.toLong
    }
    Duration.millis(millis)
  }

  def durationToString(dur: Long) = {
    val millis = dur % 1000
    val left = dur / 1000
    val hour = left / 3600
    val minute = left % 3600 / 60
    val second = (left % 3600) % 60
    f"$hour%02d:$minute%02d:$second%02d.$millis%03d"
  }

  val imageCache = collection.mutable.Map.empty[Int, Image]
  var currentSlide: Int = -1
  var currentStream: Int = -1
  var streams: List[ParleysStream] = _
  var slides: List[Slide] = _
  var players: List[MediaPlayer] = _
  var overallDuration: Double = _
  var overallPosition: Double = _
  var bindedOnPlayerCurrentTimeChanged: ChangeListener[Duration] = _

  def getExt(url: String): String = {
    Files.getFileExtension(new URL(url).getFile)
  }

  def onPlayerCurrentTimeChanged(player: MediaPlayer, streamIndex: Int) = new ChangeListener[Duration]() {
    def changed(observable: ObservableValue[_ <: Duration], oldValue: Duration, newValue: Duration) {
      val pos = newValue.toMillis
      overallPosition = streams.slice(0, streamIndex).map(_.duration).sum + pos
      updateValues()
      for ((slide, index) <- slides.zipWithIndex.takeWhile { case (s, _) => s.cuePoint <= overallPosition}.lastOption) {
        if (index != currentSlide) {
          slideView.setImage(imageCache.get(index).getOrElse {
            val slideImg = new Image(pkgLoader.getResourceAsStream(slide.local))
            imageCache(index) = slideImg
            slideImg
          })
          currentSlide = index
        }
      }
    }
  }

  def playStream(stream: Int, at: Double = 0): Unit = {
    val oldPlayer = mediaView.getMediaPlayer
    if (oldPlayer != null) {
      oldPlayer.pause()
    }
    val player = players(stream)
    player.setStopTime(Duration.millis(streams(stream).duration))
    mediaView.setMediaPlayer(player)
    if (player.getStatus == Status.PAUSED || player.getStatus == Status.PLAYING) {
      player.setStartTime(Duration.ZERO)
      player.seek(Duration.millis(at))
    } else {
      player.setStartTime(Duration.millis(at))
    }
    player.play()
  }

  def initPlayer(player: MediaPlayer, streamIndex : Int): Unit = {
    player.setOnPlaying(new Runnable {
      def run() {
        if(currentStream != streamIndex){
          slideView.setImage(null)
          currentSlide = -1
          currentStream = streamIndex
        }
        //          //        if (stopRequested) {
        //          //          player.pause()
        //          //          stopRequested = false
        //          //        } else {
        playButton.setGraphic(imageViewPause)
        //          //        }
      }
    })
    player.setOnPaused(new Runnable {
      def run() {
        playButton.setGraphic(imageViewPlay)
      }
    })
    player.setOnReady(new Runnable {
      def run() {
      }
    })
    player.setCycleCount(0)

    player.setOnError(new Runnable {
      def run() {
        println("Video error " + player.getError)
      }
    })
    player.setOnEndOfMedia(new Runnable() {
      def run() {
        onEndOfStream()
      }
    })
    player.currentTimeProperty().addListener(onPlayerCurrentTimeChanged(player, streamIndex))
  }

  def onEndOfStream(): Unit = {
    if (currentStream == streams.size - 1) {
      // last
      playButton.setGraphic(imageViewPlay)
      //      stopRequested = true
      //      atEndOfMedia = true
    } else {
      playStream(currentStream + 1)
    }
  }

  def loadPackage(pkgFile: File) {
    //    val name = Files.getNameWithoutExtension(pkgFile.getName)
    pkgLoader = new JarClassLoader(pkgFile.toURI.toURL)
    val assets = parse(io.Source.fromInputStream(pkgLoader.getResourceAsStream("assets.json")).mkString)
    streams = (assets \ "streams").extract[List[ParleysStream]]
    slides = (assets \ "slides").extract[List[Slide]]
    overallDuration = streams.map(_.duration).sum
    players = streams.map(stream => new MediaPlayer(new Media(pkgLoader.getResource(stream.local).toExternalForm)))
    players.zipWithIndex.foreach(Function.tupled(initPlayer _))
    imageCache.clear()
    playStream(0)
  }

  def updateValues() {
    if (playTime != null && progressBar != null && volumeSlider != null) {
      Platform.runLater(new Runnable {
        def run() {
          playTime.setText(formatTime(overallPosition.toLong, overallDuration.toLong))
          if (!progressBar.isDisabled && overallDuration > 0) {
            progressBar.setProgress(overallPosition / overallDuration)
          }
          if (!volumeSlider.isValueChanging) {
            volumeSlider.setValue(math.round(mediaView.getMediaPlayer.getVolume * volumeSlider.getMax))
          }
        }
      })
    }
  }

  override def layoutChildren() {
    if (mediaView != null && this.getBottom != null) {
      mediaView.setFitWidth(this.getWidth - slideView.getFitWidth)
      mediaView.setFitHeight(this.getHeight - getBottom.prefHeight(-1))
    }
    super.layoutChildren()
    //    if (mediaView != null && this.getCenter != null) {
    //      mediaView.setTranslateX((getCenter.asInstanceOf[Pane].getWidth - mediaView.prefWidth(-1)) / 2)
    //      mediaView.setTranslateY((getCenter.asInstanceOf[Pane].getHeight - mediaView.prefHeight(-1)) / 2)
    //    }
  }

  def formatTime(elapsed: Long, duration: Long) = {
    def toStringWithoutMillis(dur: Long) = durationToString(dur).takeWhile(_ != '.')
    toStringWithoutMillis(elapsed) + "/" + toStringWithoutMillis(duration)
  }

  setMinSize(1194, 414)

  //  override def computeMinWidth(height: Double) = mediaBar.prefWidth(-1)
  //  override def computeMinHeight(width: Double) = 200

}


